#ifndef __TPS_SERIALIZER_H__
#define __TPS_SERIALIZER_H__

#include "private/tps_non_copyable.h"
#include "private/tps_utils.h"

#include <memory>
#include <vector>

namespace Tps
{

	namespace Private
	{

		template <typename T, typename TList>
		struct CheckAsIsType
		{
			enum { Index = GetTypeIndex<T, TList>::Index};
			enum { IsTrue = (static_cast<unsigned>(Index) < static_cast<unsigned>(GetTypeListLength<TList>::Length)) };
		};

		template <typename T, typename TList>
		struct CheckAsTypeWrapper
		{
			enum { Index = GetTypeIndex<TypeWrapper<T>, TList>::Index };
			enum { IsTrue = (static_cast<unsigned>(Index) < static_cast<unsigned>(GetTypeListLength<TList>::Length)) };
		};

		template <typename T, typename TList>
		struct CheckAsRawType
		{
			enum { Index = GetTypeIndex<typename ConstCast<typename TypeExtractor<T>::Type>::Type, TList>::Index };
			enum { IsTrue = (static_cast<unsigned>(Index) < static_cast<unsigned>(GetTypeListLength<TList>::Length)) };
		};

		template
		<
			typename T,
			typename SupportedTypeCheckerList
		>
		struct GetSupportedTypeIndex;

		template
		<
			typename T,
			typename SupportedTypeCheckerList,
			bool isExists
		>
		struct GetSupportedTypeIndexImpl
		{
			enum { Index = GetSupportedTypeIndex<T, typename SupportedTypeCheckerList::Tail>::Index };
		};

		template
		<
			typename T,
			typename SupportedTypeCheckerList
		>
		struct GetSupportedTypeIndexImpl<T, SupportedTypeCheckerList, true>
		{
			typedef typename SupportedTypeCheckerList::Head CurChecker;
			enum { Index = CurChecker::Index };
		};

		template
		<
			typename T,
			typename SupportedTypeCheckerList
		>
		struct GetSupportedTypeIndex
		{
			typedef typename SupportedTypeCheckerList::Head CurChecker;
			enum { Index = GetSupportedTypeIndexImpl<T, SupportedTypeCheckerList, CurChecker::IsTrue>::Index };
		};
        
		template <typename SupportedTypes>
		class MtdPackBase
		{
		protected:
			MtdPackBase()
			{
			}
			template <typename ParamType>
			unsigned GetTypeIndex() const
			{
				return GetSupportedTypeIndex
							<
								ParamType,
								TypeListBuilder
								<
									CheckAsIsType<ParamType, SupportedTypes>,
									CheckAsTypeWrapper<ParamType, SupportedTypes>,
									CheckAsRawType<ParamType, SupportedTypes>
								>
							>::Index;
			}
			template <typename ParamType>
			RefPtrVal HowToSend() const
			{
				return static_cast<RefPtrVal>(ParamSendBy<ParamType>::SendType);
			}
			template <typename ParamType>
			bool IsConstParam() const
			{
				return !!IsConstType<typename TypeExtractor<ParamType>::Type>::IsConst;
			}
		};

		template
		<
			typename PackerType,
			typename SupportedTypes
		>
		class OutMtdPack
			: public PackerType
			, protected MtdPackBase<SupportedTypes>
            , public IDisposable
		{
		public:
			template <typename ParamType>
			void PutParameter(unsigned index, ParamType param)
			{
				PackerType::template PutParameter<ParamType>(index,
                    MtdPackBase<SupportedTypes>::template GetTypeIndex<ParamType>(),
					MtdPackBase<SupportedTypes>::template HowToSend<ParamType>(),
                    MtdPackBase<SupportedTypes>::template IsConstParam<ParamType>(),
                    param);
			}
			template <typename ParamType>
			void PutRetValue(ParamType retValue)
			{
				PackerType::template PutRetValue<ParamType>(
                    MtdPackBase<SupportedTypes>::template GetTypeIndex<ParamType>(),
					MtdPackBase<SupportedTypes>::template HowToSend<ParamType>(),
                    MtdPackBase<SupportedTypes>::template IsConstParam<ParamType>(),
                    retValue);
			}
		};

		template
		<
			typename UnpackerType,
			typename SupportedTypes
		>
		class InMtdPack
			: public UnpackerType
			, protected  MtdPackBase<SupportedTypes>
            , public IDisposable
		{
		public:
			InMtdPack(void const *buffer, unsigned long bufferLen)
				: UnpackerType(buffer, bufferLen)
			{
			}
			template <typename ParamType>
			void GetParameter(unsigned index, typename RemoveRef<typename ConstCast<ParamType>::Type>::Type **param)
			{
				UnpackerType::template GetParameter<ParamType>(index,
                    MtdPackBase<SupportedTypes>::template GetTypeIndex<ParamType>(),
                    MtdPackBase<SupportedTypes>::template HowToSend<ParamType>(),
                    MtdPackBase<SupportedTypes>::template IsConstParam<ParamType>(),
                    param);
			}
            template <typename RetType>
			void GetRetValue(typename RemoveRef<typename ConstCast<RetType>::Type>::Type **ret)
            {
                UnpackerType::template GetRetValue<RetType>(
                    MtdPackBase<SupportedTypes>::template GetTypeIndex<RetType>(),
                    MtdPackBase<SupportedTypes>::template HowToSend<RetType>(),
                    MtdPackBase<SupportedTypes>::template IsConstParam<RetType>(),
                    ret);
            }
		};

	}

	template <typename PackerType, typename UnpackerType, typename TransportType>
	class RemoteMethod
		: private Tps::Private::NonCopyable
	{
	public:
		typedef typename PackerType::InstIdType InstIdType;
		typedef typename PackerType::ClsIdType ClsIdType;
		typedef typename PackerType::MtdIdType MtdIdType;
		typedef typename PackerType::SupportedTypes SupportedTypeList;
		typedef Tps::Private::OutMtdPack<PackerType, SupportedTypeList> Serializer;
		typedef Tps::Private::InMtdPack<UnpackerType, SupportedTypeList> Deserializer;
		typedef PackerType SerializerType;
		typedef UnpackerType DeserializerType;
		typedef typename Deserializer::BufferType BufferType;

		RemoteMethod(TransportType *transport)
			: Transport(transport)
		{
		}

		std::auto_ptr<Serializer> CreateSerializer(InstIdType const &instId, ClsIdType const &clsId,
													MtdIdType const &mtdId, unsigned paramsCount, bool isConst)
		{
			std::auto_ptr<Serializer> Packer(new Serializer);
			Packer->PutInstId(instId);
			Packer->PutClsId(clsId);
			Packer->PutMtdId(mtdId);
			Packer->PutParamsCount(paramsCount);
			Packer->PutMtdModifier(isConst);
			return Packer;
		}

		std::auto_ptr<Deserializer> CreateDeserializer(void const *buffer, unsigned long bufferLen)
		{
			std::auto_ptr<Deserializer> Unpacker(new Deserializer(buffer, bufferLen));
			return Unpacker;
		}

		std::auto_ptr<Deserializer> CallRemoteMethod(std::auto_ptr<Serializer> pack)
		{
			std::auto_ptr<BufferType> Buffer = Transport->SendRequest(pack->GetBuffer(), pack->GetBufferLen());
			return CreateDeserializer(&((*Buffer.get())[0]), Buffer->size());
		}

	private:
		TransportType *Transport;
	};

}

#endif	// !__TPS_SERIALIZER_H__
